//
//  NSObject+LTM_Runtime.m
//  LTM_Lib_System_Objc
//
//  Created by 柯南 on 2021/12/17.
//

#import "NSObject+LTM_Runtime.h"

#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (LTM_Runtime)

+ (BOOL)addInstanceMethodWithSelectorName:(NSString *)selectorName block:(void (^)(id))block {
    // don't accept nil name
    NSParameterAssert(selectorName);

    // don't accept NULL block
    NSParameterAssert(block);

// See http://stackoverflow.com/questions/6357663/casting-a-block-to-a-void-for-dynamic-class-method-resolution

#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_7
    void *impBlockForIMP = (void *)objc_unretainedPointer(block);
#else
    id impBlockForIMP = (__bridge id)(__bridge void *)(block);
#endif

    IMP myIMP = imp_implementationWithBlock(impBlockForIMP);

    SEL selector = NSSelectorFromString(selectorName);
    return class_addMethod(self, selector, myIMP, "v@:");
}

#pragma mark - Method Swizzling

+ (void)swizzleMethod:(SEL)selector withMethod:(SEL)otherSelector {
    // my own class is being targetted
    Class myClass = [self class];

    // get the methods from the selectors
    Method originalMethod = class_getInstanceMethod(myClass, selector);
    Method otherMethod    = class_getInstanceMethod(myClass, otherSelector);

    if (class_addMethod(myClass, selector, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod))) {
        class_replaceMethod(myClass, otherSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, otherMethod);
    }
}

+ (void)swizzleClassMethod:(SEL)selector withMethod:(SEL)otherSelector {
    // my own class is being targetted
    Class myClass = [self class];

    // get the methods from the selectors
    Method originalMethod = class_getClassMethod(myClass, selector);
    Method otherMethod    = class_getClassMethod(myClass, otherSelector);

    //    if (class_addMethod(c, selector, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod)))
    //    {
    //        class_replaceMethod(c, otherSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    //    }
    //    else
    //    {
    method_exchangeImplementations(originalMethod, otherMethod);
    //    }
}

- (void)setAssociateValue:(id)value withKey:(void *)key {
    objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setAssociateWeakValue:(id)value withKey:(void *)key {
    objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN);
}

- (void)setAssociateCopyValue:(id)value withKey:(void *)key {
    objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (void)removeAssociatedValues {
    objc_removeAssociatedObjects(self);
}

- (id)associatedValueForKey:(void *)key {
    return objc_getAssociatedObject(self, key);
}

+ (NSString *)className {
    return NSStringFromClass(self);
}

- (NSString *)className {
    return [NSString stringWithUTF8String:class_getName([self class])];
}

- (NSDictionary *)propertyDictionary {
    //创建可变字典
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    unsigned int outCount;
    objc_property_t *props = class_copyPropertyList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t prop = props[i];
        NSString *propName   = [[NSString alloc] initWithCString:property_getName(prop) encoding:NSUTF8StringEncoding];
        id propValue         = [self valueForKey:propName];
        [dict setObject:propValue ?: [NSNull null] forKey:propName];
    }
    free(props);
    return dict;
}

- (NSArray *)propertyKeys {
    return [[self class] propertyKeys];
}

+ (NSArray *)propertyKeys {
    unsigned int propertyCount    = 0;
    objc_property_t *properties   = class_copyPropertyList(self, &propertyCount);
    NSMutableArray *propertyNames = [NSMutableArray array];
    for (unsigned int i = 0; i < propertyCount; ++i) {
        objc_property_t property = properties[i];
        const char *name         = property_getName(property);
        [propertyNames addObject:[NSString stringWithUTF8String:name]];
    }
    free(properties);
    return propertyNames;
}

- (NSArray *)propertiesInfo {
    return [[self class] propertiesInfo];
}

/**
 *  @brief 属性列表与属性的各种信息
 */
+ (NSArray *)propertiesInfo {
    NSMutableArray *propertieArray = [NSMutableArray array];

    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);

    for (int i = 0; i < propertyCount; i++) {
        [propertieArray addObject:({

                            NSDictionary *dictionary = [self dictionaryWithProperty:properties[i]];

                            dictionary;
                        })];
    }

    free(properties);

    return propertieArray;
}

+ (NSArray *)propertiesWithCodeFormat {
    NSMutableArray *array = [NSMutableArray array];

    NSArray *properties = [[self class] propertiesInfo];

    for (NSDictionary *item in properties) {
        NSMutableString *format = ({

            NSMutableString *formatString = [NSMutableString stringWithFormat:@"@property "];
            //attribute
            NSArray *attribute = [item objectForKey:@"attribute"];
            attribute          = [attribute sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
                return [obj1 compare:obj2 options:NSNumericSearch];
            }];
            if (attribute && attribute.count > 0) {
                NSString *attributeStr = [NSString stringWithFormat:@"(%@)", [attribute componentsJoinedByString:@", "]];

                [formatString appendString:attributeStr];
            }

            //type
            NSString *type = [item objectForKey:@"type"];
            if (type) {
                [formatString appendString:@" "];
                [formatString appendString:type];
            }

            //name
            NSString *name = [item objectForKey:@"name"];
            if (name) {
                [formatString appendString:@" "];
                [formatString appendString:name];
                [formatString appendString:@";"];
            }

            formatString;
        });

        [array addObject:format];
    }

    return array;
}

- (NSArray *)methodList {
    u_int count;
    NSMutableArray *methodList = [NSMutableArray array];
    Method *methods            = class_copyMethodList([self class], &count);
    for (int i = 0; i < count; i++) {
        SEL name          = method_getName(methods[i]);
        NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        [methodList addObject:strName];
    }
    free(methods);
    return methodList;
}

- (NSArray *)methodListInfo {
    u_int count;
    NSMutableArray *methodList = [NSMutableArray array];
    Method *methods            = class_copyMethodList([self class], &count);
    for (int i = 0; i < count; i++) {
        NSMutableDictionary *info = [NSMutableDictionary dictionary];

        Method method = methods[i];
        //        IMP imp = method_getImplementation(method);
        SEL name = method_getName(method);
        // 返回方法的参数的个数
        int argumentsCount = method_getNumberOfArguments(method);
        //获取描述方法参数和返回值类型的字符串
        const char *encoding = method_getTypeEncoding(method);
        //取方法的返回值类型的字符串
        const char *returnType = method_copyReturnType(method);

        NSMutableArray *arguments = [NSMutableArray array];
        for (int index = 0; index < argumentsCount; index++) {
            // 获取方法的指定位置参数的类型字符串
            char *arg = method_copyArgumentType(method, index);
            //            NSString *argString = [NSString stringWithCString:arg encoding:NSUTF8StringEncoding];
            [arguments addObject:[[self class] decodeType:arg]];
        }

        NSString *returnTypeString = [[self class] decodeType:returnType];
        NSString *encodeString     = [[self class] decodeType:encoding];
        NSString *nameString       = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];

        [info setObject:arguments forKey:@"arguments"];
        [info setObject:[NSString stringWithFormat:@"%d", argumentsCount] forKey:@"argumentsCount"];
        [info setObject:returnTypeString forKey:@"returnType"];
        [info setObject:encodeString forKey:@"encode"];
        [info setObject:nameString forKey:@"name"];
        //        [info setObject:imp_f forKey:@"imp"];
        [methodList addObject:info];
    }
    free(methods);
    return methodList;
}

+ (NSArray *)methodList {
    u_int count;
    NSMutableArray *methodList = [NSMutableArray array];
    Method *methods            = class_copyMethodList([self class], &count);
    for (int i = 0; i < count; i++) {
        SEL name          = method_getName(methods[i]);
        NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        [methodList addObject:strName];
    }
    free(methods);

    return methodList;
}

- (id)deepCopy {
    id obj = nil;
    @try {
        obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self]];
    }
    @catch (NSException *exception) {
        NSLog(@"%@", exception);
    }
    return obj;
}

- (id)deepCopyWithArchiver:(Class)archiver unarchiver:(Class)unarchiver {
    id obj = nil;
    @try {
        obj = [unarchiver unarchiveObjectWithData:[archiver archivedDataWithRootObject:self]];
    }
    @catch (NSException *exception) {
        NSLog(@"%@", exception);
    }
    return obj;
}

+ (NSDictionary *)dictionaryWithProperty:(objc_property_t)property {
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    //name
    NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
    [result setObject:propertyName forKey:@"name"];

    //attribute

    NSMutableDictionary *attributeDictionary = ({

        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

        unsigned int attributeCount;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attributeCount);

        for (int i = 0; i < attributeCount; i++) {
            NSString *name  = [NSString stringWithCString:attrs[i].name encoding:NSUTF8StringEncoding];
            NSString *value = [NSString stringWithCString:attrs[i].value encoding:NSUTF8StringEncoding];
            [dictionary setObject:value forKey:name];
        }

        free(attrs);

        dictionary;
    });

    NSMutableArray *attributeArray = [NSMutableArray array];

    /***
     R           | The property is read-only (readonly).
     C           | The property is a copy of the value last assigned (copy).
     &           | The property is a reference to the value last assigned (retain).
     N           | The property is non-atomic (nonatomic).
     G<name>     | The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).
     S<name>     | The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).
     D           | The property is dynamic (@dynamic).
     W           | The property is a weak reference (__weak).
     P           | The property is eligible for garbage collection.
     t<encoding> | Specifies the type using old-style encoding.
     */

    //R
    if ([attributeDictionary objectForKey:@"R"])
        [attributeArray addObject:@"readonly"];

    //C
    if ([attributeDictionary objectForKey:@"C"])
        [attributeArray addObject:@"copy"];

    //&
    if ([attributeDictionary objectForKey:@"&"])
        [attributeArray addObject:@"strong"];

    //N
    if ([attributeDictionary objectForKey:@"N"])
        [attributeArray addObject:@"nonatomic"];
    else
        [attributeArray addObject:@"atomic"];

    //G<name>
    if ([attributeDictionary objectForKey:@"G"])
        [attributeArray addObject:[NSString stringWithFormat:@"getter=%@", [attributeDictionary objectForKey:@"G"]]];

    //S<name>
    if ([attributeDictionary objectForKey:@"S"])
        [attributeArray addObject:[NSString stringWithFormat:@"setter=%@", [attributeDictionary objectForKey:@"G"]]];

    //D
    if ([attributeDictionary objectForKey:@"D"])
        [result setObject:[NSNumber numberWithBool:YES] forKey:@"isDynamic"];
    else
        [result setObject:[NSNumber numberWithBool:NO] forKey:@"isDynamic"];

    //W
    if ([attributeDictionary objectForKey:@"W"])
        [attributeArray addObject:@"weak"];

    //P
    if ([attributeDictionary objectForKey:@"P"])
        //TODO:P | The property is eligible for garbage collection.

        //T
        if ([attributeDictionary objectForKey:@"T"]) {
            /*
             c               A char
             i               An int
             s               A short
             l               A long l is treated as a 32-bit quantity on 64-bit programs.
             q               A long long
             C               An unsigned char
             I               An unsigned int
             S               An unsigned short
             L               An unsigned long
             Q               An unsigned long long
             f               A float
             d               A double
             B               A C++ bool or a C99 _Bool
             v               A void
             *               A character string (char *)
             @               An object (whether statically typed or typed id)
             #               A class object (Class)
             :               A method selector (SEL)
             [array type]    An array
             {name=type...}  A structure
             (name=type...)  A union
             bnum            A bit field of num bits
             ^type           A pointer to type
             ?               An unknown type (among other things, this code is used for function pointers)
             
             */

            NSDictionary *typeDic = @{ @"c": @"char",
                                       @"i": @"int",
                                       @"s": @"short",
                                       @"l": @"long",
                                       @"q": @"long long",
                                       @"C": @"unsigned char",
                                       @"I": @"unsigned int",
                                       @"S": @"unsigned short",
                                       @"L": @"unsigned long",
                                       @"Q": @"unsigned long long",
                                       @"f": @"float",
                                       @"d": @"double",
                                       @"B": @"BOOL",
                                       @"v": @"void",
                                       @"*": @"char *",
                                       @"@": @"id",
                                       @"#": @"Class",
                                       @":": @"SEL",
            };
            //TODO:An array
            NSString *key = [attributeDictionary objectForKey:@"T"];

            id type_str = [typeDic objectForKey:key];

            if (type_str == nil) {
                if ([[key substringToIndex:1] isEqualToString:@"@"] && [key rangeOfString:@"?"].location == NSNotFound) {
                    type_str = [[key substringWithRange:NSMakeRange(2, key.length - 3)] stringByAppendingString:@"*"];
                } else if ([[key substringToIndex:1] isEqualToString:@"^"]) {
                    id str = [typeDic objectForKey:[key substringFromIndex:1]];

                    if (str) {
                        type_str = [NSString stringWithFormat:@"%@ *", str];
                    }
                } else {
                    type_str = @"unknow";
                }
            }

            [result setObject:type_str forKey:@"type"];
        }

    [result setObject:attributeArray forKey:@"attribute"];

    return result;
}

+ (NSString *)decodeType:(const char *)cString {
    if (!strcmp(cString, @encode(char)))
        return @"char";
    if (!strcmp(cString, @encode(int)))
        return @"int";
    if (!strcmp(cString, @encode(short)))
        return @"short";
    if (!strcmp(cString, @encode(long)))
        return @"long";
    if (!strcmp(cString, @encode(long long)))
        return @"long long";
    if (!strcmp(cString, @encode(unsigned char)))
        return @"unsigned char";
    if (!strcmp(cString, @encode(unsigned int)))
        return @"unsigned int";
    if (!strcmp(cString, @encode(unsigned short)))
        return @"unsigned short";
    if (!strcmp(cString, @encode(unsigned long)))
        return @"unsigned long";
    if (!strcmp(cString, @encode(unsigned long long)))
        return @"unsigned long long";
    if (!strcmp(cString, @encode(float)))
        return @"float";
    if (!strcmp(cString, @encode(double)))
        return @"double";
    if (!strcmp(cString, @encode(bool)))
        return @"bool";
    if (!strcmp(cString, @encode(_Bool)))
        return @"_Bool";
    if (!strcmp(cString, @encode(void)))
        return @"void";
    if (!strcmp(cString, @encode(char *)))
        return @"char *";
    if (!strcmp(cString, @encode(id)))
        return @"id";
    if (!strcmp(cString, @encode(Class)))
        return @"class";
    if (!strcmp(cString, @encode(SEL)))
        return @"SEL";
    if (!strcmp(cString, @encode(BOOL)))
        return @"BOOL";

    //@TODO: do handle bitmasks
    NSString *result = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding];

    if ([[result substringToIndex:1] isEqualToString:@"@"] && [result rangeOfString:@"?"].location == NSNotFound) {
        result = [[result substringWithRange:NSMakeRange(2, result.length - 3)] stringByAppendingString:@"*"];
    } else {
        if ([[result substringToIndex:1] isEqualToString:@"^"]) {
            result = [NSString stringWithFormat:@"%@ *",
                                                [NSString decodeType:[[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]]];
        }
    }
    return result;
}

@end
